package com.ittao.taoaicodeapp.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import com.ittao.taoaicodeai.ai.AiCodeGenTypeRoutingService;
import com.ittao.taoaicodeapp.ai.AiCodeGenTypeRoutingServiceFactory;
import com.ittao.taoaicodeapp.core.AiCodeGeneratorFacade;
import com.ittao.taoaicodeapp.core.handler.StreamHandlerExecutor;
import com.ittao.taoaicodeapp.mapper.AppMapper;
import com.ittao.taoaicodeapp.service.AppService;
import com.ittao.taoaicodeapp.service.ChatHistoryService;
import com.ittao.taoaicodeapp.service.TaskManagerService;
import com.ittao.taoaicodeclient.innerservice.InnerScreenshotService;
import com.ittao.taoaicodeclient.innerservice.InnerUserService;
import com.ittao.taoaicodecommon.config.AppStorageConfig;
import com.ittao.taoaicodecommon.constant.AppStatusConstant;
import com.ittao.taoaicodecommon.exception.BusinessException;
import com.ittao.taoaicodecommon.exception.ErrorCode;
import com.ittao.taoaicodecommon.exception.ThrowUtils;
import com.ittao.taoaicodecommon.manage.storage.callback.impl.PerformanceMonitoringCallbackImpl;
import com.ittao.taoaicodecommon.manage.storage.enums.StorageType;
import com.ittao.taoaicodecommon.manage.storage.factory.FileStorageStrategyFactory;
import com.ittao.taoaicodecommon.manage.storage.lifecycle.observer.TempFileLifecycleObserver;
import com.ittao.taoaicodecommon.manage.storage.lifecycle.tracker.SessionActivityTracker;
import com.ittao.taoaicodecommon.manage.storage.service.CosOperationService;
import com.ittao.taoaicodecommon.manage.storage.service.TempDirectoryService;
import com.ittao.taoaicodecommon.manage.storage.strategy.FileStorageStrategy;
import com.ittao.taoaicodecommon.manage.storage.strategy.impl.CosFileStorageStrategy;
import com.ittao.taoaicodemodel.model.dto.app.AppAddRequest;
import com.ittao.taoaicodemodel.model.dto.app.AppQueryRequest;
import com.ittao.taoaicodemodel.model.entity.App;
import com.ittao.taoaicodemodel.model.entity.User;
import com.ittao.taoaicodemodel.model.enums.ChatHistoryMessageTypeEnum;
import com.ittao.taoaicodemodel.model.enums.CodeGenTypeEnum;
import com.ittao.taoaicodemodel.model.vo.app.AppVO;
import com.ittao.taoaicodemodel.model.vo.user.UserVO;
import com.mybatisflex.core.query.QueryWrapper;
import com.mybatisflex.core.update.UpdateChain;
import com.mybatisflex.spring.service.impl.ServiceImpl;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;

import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 应用 服务实现。
 *
 * @author <a href="https://gitee.com/it-heima-ht">程序员 Tao</a>
 */
@Slf4j
@Service
public class AppServiceImpl extends ServiceImpl<AppMapper, App> implements AppService {

    @DubboReference
    private InnerUserService userService;

    @Resource
    @Lazy
    private AiCodeGeneratorFacade aiCodeGeneratorFacade;

    @Resource
    private TaskManagerService taskManagerService;

    @Resource
    private ChatHistoryService chatHistoryService;

    @Resource
    private StreamHandlerExecutor streamHandlerExecutor;

    @DubboReference
    private InnerScreenshotService screenshotService;

    @Resource
    private AiCodeGenTypeRoutingServiceFactory aiCodeGenTypeRoutingServiceFactory;

    @Resource
    private FileStorageStrategyFactory fileStorageStrategyFactory;

    @Resource
    private AppStorageConfig appStorageConfig;

    @Resource
    private SessionActivityTracker sessionActivityTracker;

    @Resource
    private TempFileLifecycleObserver tempFileLifecycleObserver;

    @Resource
    private CosOperationService cosOperationService;

    @Resource
    private TempDirectoryService tempDirectoryService;

    @Resource
    private PerformanceMonitoringCallbackImpl performanceMonitoringCallback;

    /**
     * 与 AI 聊天，生成代码
     * @param appId 应用 ID
     * @param message 提示词
     * @param loginUser 登录用户
     * @return
     */
    @Override
    public Flux<String> chatToGenCode(Long appId, String message, User loginUser) {
        // 1. 参数校验
        ThrowUtils.throwIf(appId == null, ErrorCode.PARAMS_ERROR, "应用 ID 错误");
        ThrowUtils.throwIf(StrUtil.isBlank(message), ErrorCode.PARAMS_ERROR, "提示词不能为空");
        // 2. 查询应用信息
        App app = this.getById(appId);
        ThrowUtils.throwIf(app == null, ErrorCode.NOT_FOUND_ERROR, "应用不存在");
        // 3. 权限校验，仅本人可以和自己的应用对话
        if (!app.getUserId().equals(loginUser.getId())) {
            throw new BusinessException(ErrorCode.NO_AUTH_ERROR, "无权限使用该应用");
        }
        // 4. 获取应用的代码生成类型
        String codeGenType = app.getCodeGenType();
        CodeGenTypeEnum codeGenTypeEnum = CodeGenTypeEnum.getEnumByValue(codeGenType);
        if (codeGenTypeEnum == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "应用代码生成类型错误");
        }

        // 5. 使用appId作为任务标识，简化任务ID管理
        String taskId = appId.toString();

        // 记录日志，便于调试
        log.info("开始生成任务，taskId: {}, appId: {}", taskId, appId);

        // 6. 先更新数据库记录当前任务ID和状态
        App updateApp = new App();
        updateApp.setId(appId);
        updateApp.setCurrentTaskId(taskId);
        updateApp.setGenerateStatus(AppStatusConstant.GENERATE_STATUS_GENERATING);
        updateApp.setEditTime(LocalDateTime.now());
        boolean updateResult = this.updateById(updateApp);
        ThrowUtils.throwIf(!updateResult, ErrorCode.SYSTEM_ERROR, "更新应用状态失败");

        // 7. 在调用 AI 前，先保存用户对话消息到数据库中
        boolean addResult = chatHistoryService.addChatMessage(appId, message, ChatHistoryMessageTypeEnum.USER.getValue(), loginUser.getId());
        ThrowUtils.throwIf(!addResult, ErrorCode.SYSTEM_ERROR, "保存用户对话消息失败");

        // 8. 调用 AI 生成代码，获取原始流（使用支持VIP限制的新方法）
        Flux<String> originCodeStream = aiCodeGeneratorFacade.generateAndSaveCodeStream(message, codeGenTypeEnum, appId, loginUser);

        // 9. 收集 AI 响应的内容，并且在完成后保存记录到对话历史
        originCodeStream = streamHandlerExecutor.doExecute(originCodeStream, chatHistoryService, appId, loginUser, codeGenTypeEnum);

        // 10. 使用任务管理器包装流，支持中断功能
        return taskManagerService.startGenerateTask(taskId, appId, originCodeStream)
                .doOnComplete(() -> {
                    // 生成完成时清除任务ID
                    updateAppTaskStatus(appId, null, AppStatusConstant.GENERATE_STATUS_COMPLETED);
                })
                .doOnError(error -> {
                    // 生成出错时清除任务ID
                    updateAppTaskStatus(appId, null, AppStatusConstant.GENERATE_STATUS_FAILED);
                })
                .doOnCancel(() -> {
                    // 生成取消时清除任务ID
                    updateAppTaskStatus(appId, null, AppStatusConstant.GENERATE_STATUS_INTERRUPTED);
                });
    }

    /**
     * 新增应用
     * @param appAddRequest 应用添加请求
     * @param loginUser 登录用户
     * @return 应用 ID
     */
    @Override
    public Long addApp(AppAddRequest appAddRequest, User loginUser) {
        // 参数校验
        String initPrompt = appAddRequest.getInitPrompt();
        ThrowUtils.throwIf(StrUtil.isBlank(initPrompt), ErrorCode.PARAMS_ERROR, "初始化 prompt 不能为空");
        // 限制message参数长度（建议4000字符以内）
        ThrowUtils.throwIf(initPrompt.length() > 4000, ErrorCode.PARAMS_ERROR, "提示词过长，请控制在4000字符以内");
        // 构造入库对象
        App app = new App();
        BeanUtil.copyProperties(appAddRequest, app);
        app.setUserId(loginUser.getId());
        // 使用AI生成应用名称
        String appName = this.generateAppName(initPrompt);
        app.setAppName(appName);
        // 使用 AI 智能路由选择代码生成类型（多例模式）
        AiCodeGenTypeRoutingService aiCodeGenTypeRoutingService = aiCodeGenTypeRoutingServiceFactory.createAiCodeGenTypeRoutingService();
        CodeGenTypeEnum codeGenTypeEnum = aiCodeGenTypeRoutingService.routeCodeGenType(initPrompt);
        app.setCodeGenType(codeGenTypeEnum.getValue());
        // 初始化状态字段
        app.setGenerateStatus(AppStatusConstant.GENERATE_STATUS_NOT_GENERATED);
        app.setDeployStatus(AppStatusConstant.DEPLOY_STATUS_NOT_DEPLOYED);
        // 插入数据库
        boolean result = this.save(app);
        ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR);
        return app.getId();
    }

    /**
     * 部署应用
     * @param appId 应用 ID
     * @param loginUser 登录用户
     * @return 可访问的部署地址
     */
    @Override
    public String deployApp(Long appId, User loginUser) {
        // 1. 参数校验
        ThrowUtils.throwIf(appId == null || appId <= 0, ErrorCode.PARAMS_ERROR, "应用 ID 错误");
        ThrowUtils.throwIf(loginUser == null, ErrorCode.NOT_LOGIN_ERROR, "用户未登录");
        // 2. 查询应用信息
        App app = this.getById(appId);
        ThrowUtils.throwIf(app == null, ErrorCode.NOT_FOUND_ERROR, "应用不存在");
        // 3. 权限校验，仅本人可以部署自己的应用
        if (!app.getUserId().equals(loginUser.getId())) {
            throw new BusinessException(ErrorCode.NO_AUTH_ERROR, "无权限部署该应用");
        }

        // 4. 检查是否正在部署中
        if (app.getDeployStatus() != null && app.getDeployStatus().equals(AppStatusConstant.DEPLOY_STATUS_DEPLOYING)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "应用正在部署中，请稍后再试");
        }

        // 5. 检查是否已有 deployKey
        String deployKey = app.getDeployKey();
        // 如果没有，则生成 10 位 deployKey（字母 ＋ 数字）
        if (StrUtil.isBlank(deployKey)) {
            deployKey = RandomUtil.randomString(10);
        }

        // 6. 获取代码生成类型，使用存储策略构建源目录路径
        String codeGenType = app.getCodeGenType();
        String sourceDirName = codeGenType + "_" + appId;

        // 使用存储策略构建code_output路径
        FileStorageStrategy storageStrategy = fileStorageStrategyFactory.getCurrentStrategy();
        String codeOutputPath = storageStrategy.buildCodeOutputPath(String.valueOf(app.getUserId()), sourceDirName);

        try {
            // 7. 先更新状态为部署中
            App deployingApp = new App();
            deployingApp.setId(appId);
            deployingApp.setDeployStatus(AppStatusConstant.DEPLOY_STATUS_DEPLOYING);
            deployingApp.setEditTime(LocalDateTime.now());
            boolean updateDeployingResult = this.updateById(deployingApp);
            ThrowUtils.throwIf(!updateDeployingResult, ErrorCode.SYSTEM_ERROR, "更新部署状态失败");

            // 8. 确保code_output目录存在且包含文件，如果不存在或为空则从COS拉取
            File codeOutputDir = new File(codeOutputPath);

            // 检查目录是否存在且包含文件
            boolean needDownload = !codeOutputDir.exists() ||
                    !codeOutputDir.isDirectory() ||
                    (codeOutputDir.listFiles() == null || Objects.requireNonNull(codeOutputDir.listFiles()).length == 0);

            if (needDownload) {
                log.info("本地code_output目录不存在或为空，尝试从COS拉取: {}", codeOutputPath);
                downloadFromCosIfNeeded(app.getUserId(), appId, codeGenType, codeOutputPath);

                // 重新检查code_output目录
                codeOutputDir = new File(codeOutputPath);
                ThrowUtils.throwIf(!codeOutputDir.exists() || !codeOutputDir.isDirectory() ||
                                (codeOutputDir.listFiles() == null || Objects.requireNonNull(codeOutputDir.listFiles()).length == 0),
                        ErrorCode.PARAMS_ERROR, "应用代码路径不存在或为空，本地和COS均无法获取，请先生成应用");
            }

            // 9. 根据代码生成类型确定实际源路径
            String actualSourcePath;
            CodeGenTypeEnum codeGenTypeEnum = CodeGenTypeEnum.getEnumByValue(codeGenType);

            // Vue项目使用dist目录，HTML和多文件模式使用源目录
            if (codeGenTypeEnum == CodeGenTypeEnum.VUE_PROJECT) {
                actualSourcePath = codeOutputPath + "/dist";
            } else {
                actualSourcePath = codeOutputPath;
            }

            // 10. 验证实际部署源路径是否存在，如果不存在且为COS策略则尝试从COS下载
            Path actualPath = Paths.get(actualSourcePath);
            if (!Files.exists(actualPath)) {
                log.warn("实际部署源路径不存在: actualSourcePath={}, codeOutputPath={}",
                        actualSourcePath, codeOutputPath);

                // 检查当前是否为COS策略
                FileStorageStrategy currentStrategy = fileStorageStrategyFactory.getCurrentStrategy();
                if (StorageType.COS.getCode().equals(currentStrategy.getStorageType().getCode())) {
                    log.info("COS策略下实际部署源路径不存在，尝试重新从COS下载完整项目: {}", actualSourcePath);
                    try {
                        // 重新从COS下载完整项目（可能包含dist目录）
                        downloadFromCosIfNeeded(app.getUserId(), appId, codeGenType, codeOutputPath);

                        // 重新检查实际部署源路径
                        if (!Files.exists(actualPath)) {
                            log.error("从COS重新下载后，实际部署源路径仍不存在: {}", actualSourcePath);
                            throw new BusinessException(ErrorCode.SYSTEM_ERROR,
                                    "部署源文件不存在，本地和COS均无法获取到完整的项目文件，请检查项目生成是否完整");
                        }
                        log.info("从COS重新下载成功，实际部署源路径现已存在: {}", actualSourcePath);
                    } catch (Exception e) {
                        log.error("COS策略下重新下载项目失败: actualSourcePath={}, error={}",
                                actualSourcePath, e.getMessage(), e);
                        throw new BusinessException(ErrorCode.SYSTEM_ERROR,
                                "从COS下载部署文件失败: " + e.getMessage());
                    }
                } else {
                    // 非COS策略，直接抛出异常
                    log.error("Local策略下实际部署源路径不存在: {}", actualSourcePath);
                    throw new BusinessException(ErrorCode.SYSTEM_ERROR, "部署源文件不存在，无法进行部署");
                }
            }

            // 额外检查：确保actualSourcePath目录不为空
            try {
                if (Files.isDirectory(actualPath)) {
                    long fileCount = Files.list(actualPath).count();
                    if (fileCount == 0) {
                        log.warn("源目录存在但为空: {}", actualSourcePath);
                        throw new BusinessException(ErrorCode.SYSTEM_ERROR, "源目录为空，无法部署");
                    }
                    log.info("源目录检查通过，包含 {} 个文件/目录", fileCount);

                    // 详细列出目录内容用于调试
                    log.info("源目录内容详情:");
                    Files.list(actualPath).forEach(path -> {
                        try {
                            if (Files.isDirectory(path)) {
                                long subFileCount = Files.list(path).count();
                                log.info("  目录: {} (包含 {} 个子项)", path.getFileName(), subFileCount);
                            } else {
                                log.info("  文件: {} (大小: {} bytes)", path.getFileName(), Files.size(path));
                            }
                        } catch (IOException e) {
                            log.warn("  无法读取: {} - {}", path.getFileName(), e.getMessage());
                        }
                    });
                } else if (!Files.exists(actualPath)) {
                    throw new BusinessException(ErrorCode.SYSTEM_ERROR, "源路径不存在: " + actualSourcePath);
                }
            } catch (IOException e) {
                log.error("检查源目录时发生错误: {}", e.getMessage(), e);
                throw new BusinessException(ErrorCode.SYSTEM_ERROR, "检查源目录失败");
            }

            // 11. 从code_output复制文件到code_deploy目录
            String deployDirPath = storageStrategy.buildCodeDeployPath(String.valueOf(app.getUserId()), deployKey);

            // 确保部署目录存在
            File deployDir = new File(deployDirPath);
            if (!deployDir.exists()) {
                boolean mkdirResult = deployDir.mkdirs();
                ThrowUtils.throwIf(!mkdirResult, ErrorCode.SYSTEM_ERROR, "当前文件可能比较大，尝试刷新一下应用，稍等片刻~~~");
            }

            // 从code_output复制文件到code_deploy目录
            log.info("开始复制文件: {} -> {}", actualSourcePath, deployDirPath);
            copyDirectoryLocally(actualSourcePath, deployDirPath);

            // 触发部署文件的自动清理机制
            if (sessionActivityTracker != null) {
                try {
                    // 生成唯一的sessionId
                    String sessionId = "deploy_cleanup_" + System.currentTimeMillis() + "_" + Math.abs(deployDirPath.hashCode());

                    // 从路径中提取userId和appId
                    String userId = String.valueOf(app.getUserId());
                    String appIdStr = String.valueOf(appId);

                    // 发布项目生成开始事件（注册会话监控）
                    sessionActivityTracker.publishProjectGenerationStart(sessionId, userId, appIdStr, deployDirPath);

                    // 发布项目生成完成事件
                    sessionActivityTracker.publishProjectGenerationComplete(sessionId, userId, appIdStr, deployDirPath);

                    log.info("已为部署目录启动自动清理机制: sessionId={}, deployDir={}", sessionId, deployDirPath);
                } catch (Exception e) {
                    log.warn("启动部署目录自动清理机制失败，但不影响部署操作: deployDir={}, 错误信息: {}", deployDirPath, e.getMessage());
                }
            }

            // 12. cos策略模式下才上传到cos中
            if (StorageType.COS.getCode().equals(storageStrategy.getStorageType().getCode())) {
                uploadDeployResultToCos(app.getUserId(), deployKey, deployDirPath);
            }

            // 13. 更新数据库为部署完成状态
            App updateApp = new App();
            updateApp.setId(appId);
            updateApp.setDeployKey(deployKey);
            updateApp.setDeployedTime(LocalDateTime.now());
            updateApp.setDeployStatus(AppStatusConstant.DEPLOY_STATUS_DEPLOYED);
            updateApp.setEditTime(LocalDateTime.now());
            boolean updateResult = this.updateById(updateApp);
            ThrowUtils.throwIf(!updateResult, ErrorCode.SYSTEM_ERROR, "应用部署失败，请重试");

            // 14. 生成可访问的 URL 地址
            String appDeployUrl = appStorageConfig.getDeployUrl() + "/" + app.getUserId() + "/code_deploy/" + deployKey + "/";
            log.info("应用部署成功，appId: {}, deployKey: {}, userId: {}", appId, deployKey, loginUser.getId());

            // 15. 部署文件的清理机制已在copyDirectoryLocally后触发，确保120秒延迟清理

            // 17. 异步生成截图并且更新应用封面
            generateAppScreenshotAsync(appId, appDeployUrl);
            return appDeployUrl;

        } catch (Exception e) {
            // 部署失败时，重置状态为未部署
            try {
                App failedApp = new App();
                failedApp.setId(appId);
                failedApp.setDeployStatus(AppStatusConstant.DEPLOY_STATUS_NOT_DEPLOYED);
                failedApp.setEditTime(LocalDateTime.now());
                this.updateById(failedApp);
            } catch (Exception resetException) {
                log.error("重置部署状态失败, appId: {}, error: {}", appId, resetException.getMessage());
            }
            log.error("应用部署失败，appId: {}, error: {}", appId, e.getMessage(), e);
            throw new BusinessException(ErrorCode.SYSTEM_ERROR, e.getMessage());
        }
    }

    /**
     * 异步生成应用解题并更新封面
     * @param appId 应用ID
     * @param appUrl 应用访问URL
     */
    @Override
    public void generateAppScreenshotAsync(Long appId, String appUrl) {
        // 启用虚拟线程并执行
        Thread.startVirtualThread(() -> {
            // 调用截图服务生成截图并上传
            String screenshotUrl = screenshotService.generateAndUploadScreenshot(appUrl);
            // 更新数据库的封面
            App updateApp = new App();
            updateApp.setId(appId);
            updateApp.setCover(screenshotUrl);
            boolean updateResult = this.updateById(updateApp);
            ThrowUtils.throwIf(!updateResult, ErrorCode.SYSTEM_ERROR, "更新应用封面字段失败");
        });
    }

    /**
     * 获取应用视图对象
     * @param app 应用
     * @return 应用视图对象
     */
    @Override
    public AppVO getAppVO(App app) {
        if (app == null) {
            return null;
        }
        AppVO appVO = new AppVO();
        BeanUtil.copyProperties(app, appVO);
        // 关联查询用户信息
        Long userId = app.getUserId();
        if (userId != null) {
            User user = userService.getById(userId);
            UserVO userVO = userService.getUserVO(user);
            appVO.setUser(userVO);
        }
        return appVO;
    }

    /**
     * 获取应用视图对象列表
     * @param appList 应用列表
     * @return
     */
    @Override
    public List<AppVO> getAppVOList(List<App> appList) {
        if (appList == null || appList.isEmpty()) {
            return null;
        }
        return appList.stream().map(this::getAppVO).collect(Collectors.toList());
    }

    /**
     * 构建应用查询条件
     * @param appQueryRequest 应用查询请求
     * @return
     */
    @Override
    public QueryWrapper getQueryWrapper(AppQueryRequest appQueryRequest) {
        if (appQueryRequest == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "请求参数为空");
        }
        Long id = appQueryRequest.getId();
        String appName = appQueryRequest.getAppName();
        String cover = appQueryRequest.getCover();
        String initPrompt = appQueryRequest.getInitPrompt();
        String codeGenType = appQueryRequest.getCodeGenType();
        String deployKey = appQueryRequest.getDeployKey();
        Integer priority = appQueryRequest.getPriority();
        Long userId = appQueryRequest.getUserId();
        // 新增字段
        Integer deployStatus = appQueryRequest.getDeployStatus();
        Integer generateStatus = appQueryRequest.getGenerateStatus();
        String currentTaskId = appQueryRequest.getCurrentTaskId();
        LocalDateTime createTimeStart = appQueryRequest.getCreateTimeStart();
        LocalDateTime createTimeEnd = appQueryRequest.getCreateTimeEnd();
        LocalDateTime deployedTimeStart = appQueryRequest.getDeployedTimeStart();
        LocalDateTime deployedTimeEnd = appQueryRequest.getDeployedTimeEnd();
        String creatorUserName = appQueryRequest.getUserName();

        String sortField = appQueryRequest.getSortField();
        String sortOrder = appQueryRequest.getSortOrder();

        QueryWrapper queryWrapper = QueryWrapper.create()
                .eq("id", id)
                .like("appName", appName)
                .like("cover", cover)
                .like("initPrompt", initPrompt)
                .eq("codeGenType", codeGenType)
                .eq("deployKey", deployKey)
                .eq("priority", priority)
                .eq("userId", userId)
                // 新增查询条件
                .eq("deployStatus", deployStatus)
                .eq("generateStatus", generateStatus)
                .eq("currentTaskId", currentTaskId)
                .ge("createTime", createTimeStart)
                .le("createTime", createTimeEnd)
                .ge("deployedTime", deployedTimeStart)
                .le("deployedTime", deployedTimeEnd);

        // 基于创建者用户名的模糊搜索
        if (StrUtil.isNotBlank(creatorUserName)) {
            // 子查询：先根据用户名模糊查询用户表，获取用户ID列表
            QueryWrapper userSubQuery = QueryWrapper.create()
                    .select("id")
                    .from("user")
                    .like("userName", creatorUserName)
                    .and("isDelete = 0");
            // 将子查询结果作为userId的IN条件
            queryWrapper.and("userId IN (" + userSubQuery.toSQL() + ")");
        }

        return queryWrapper.orderBy(sortField, "ascend".equals(sortOrder));
    }

    /**
     * 使用AI生成应用名称
     *
     * @param initPrompt 用户的初始描述
     * @return 生成的应用名称
     */
    @Override
    public String generateAppName(String initPrompt) {
        if (StrUtil.isBlank(initPrompt)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "初始化提示词不能为空");
        }

        String subAppName = initPrompt.substring(0, Math.min(initPrompt.length(), 12));
        try {
            // 调用AI服务生成应用名称
            String generatedResult = aiCodeGeneratorFacade.generateAppName(initPrompt);

            if (StrUtil.isBlank(generatedResult)) {
                log.warn("AI生成应用名称失败，使用默认逻辑");

                return subAppName;
            }

            // 解析JSON格式的返回结果
            try {
                // 尝试解析JSON格式 {"appName": "具体名称"}
                if (generatedResult.trim().startsWith("{")) {
                    // 简单的JSON解析，提取appName字段
                    String appName = extractAppNameFromJson(generatedResult);
                    if (StrUtil.isNotBlank(appName)) {
                        return cleanAppName(appName);
                    }
                }

                // 如果不是JSON格式，直接使用返回的内容
                return cleanAppName(generatedResult);

            } catch (Exception e) {
                log.warn("解析AI生成的应用名称JSON失败，使用原始内容: {}", e.getMessage());
                return cleanAppName(generatedResult);
            }

        } catch (Exception e) {
            log.error("AI生成应用名称失败，使用默认逻辑: {}", e.getMessage());
            return subAppName;
        }
    }

    /**
     * 从JSON字符串中提取appName字段
     */
    private String extractAppNameFromJson(String jsonStr) {
        try {
            // 简单的JSON解析，查找appName字段
            String cleaned = jsonStr.trim();
            if (cleaned.startsWith("{") && cleaned.endsWith("}")) {
                // 查找 "appName": "值" 的模式
                String pattern = "\"appName\"\\s*:\\s*\"([^\"]+)\"";
                java.util.regex.Pattern regex = java.util.regex.Pattern.compile(pattern);
                java.util.regex.Matcher matcher = regex.matcher(cleaned);
                if (matcher.find()) {
                    return matcher.group(1);
                }
            }
            return null;
        } catch (Exception e) {
            log.warn("JSON解析失败: {}", e.getMessage());
            return null;
        }
    }

    /**
     * 清理和验证应用名称
     */
    private String cleanAppName(String name) {
        if (StrUtil.isBlank(name)) {
            return "新应用";
        }

        String cleanedName = name.trim();
        // 移除可能的引号
        if (cleanedName.startsWith("\"") && cleanedName.endsWith("\"")) {
            cleanedName = cleanedName.substring(1, cleanedName.length() - 1);
        }

        // 确保长度在合理范围内
        if (cleanedName.length() > 15) {
            cleanedName = cleanedName.substring(0, 15);
        }
        if (cleanedName.length() < 2) {
            cleanedName = cleanedName + "应用";
        }

        return cleanedName;
    }

    /**
     * 删除应用及其关联的文件和数据
     *
     * @param appId 应用ID
     * @return 是否删除成功
     */
    @Override
    public boolean deleteAppWithCleanup(Long appId) {
        if (appId == null || appId <= 0) {
            log.error("删除应用失败：应用ID无效");
            return false;
        }

        // 1. 获取应用信息
        App app = this.getById(appId);
        if (app == null) {
            log.warn("删除应用失败：应用不存在, appId: {}", appId);
            return false;
        }

        try {
            // 2. 中断正在运行的任务（如果有的话）
            String currentTaskId = app.getCurrentTaskId();
            if (StrUtil.isNotBlank(currentTaskId)) {
                try {
                    boolean interruptResult = taskManagerService.interruptTask(currentTaskId, appId);
                    if (interruptResult) {
                        log.info("删除应用时成功中断正在运行的任务, taskId: {}, appId: {}", currentTaskId, appId);
                    } else {
                        log.warn("删除应用时中断任务失败, taskId: {}, appId: {}", currentTaskId, appId);
                    }
                } catch (Exception e) {
                    log.warn("删除应用时中断任务异常, taskId: {}, appId: {}, error: {}", currentTaskId, appId, e.getMessage());
                }
            }

            // 3. 释放监控资源（定时清理任务和会话监控）
            try {
                tempFileLifecycleObserver.manualCleanupByAppId(String.valueOf(appId), String.valueOf(app.getUserId()));
                log.info("删除应用时成功释放监控资源, appId: {}, userId: {}", appId, app.getUserId());
            } catch (Exception e) {
                log.warn("删除应用时释放监控资源失败, appId: {}, userId: {}, error: {}", appId, app.getUserId(), e.getMessage());
            }

            // 4. 根据存储策略执行不同的删除逻辑
            FileStorageStrategy storageStrategy = fileStorageStrategyFactory.getCurrentStrategy();
            if (StorageType.COS.getCode().equals(storageStrategy.getStorageType().getCode())) {
                // COS策略：先删除本地临时文件，再删除COS存储
                cleanupCosStrategyFiles(app, storageStrategy);
            } else {
                // Local策略：只删除本地文件
                cleanupLocalStrategyFiles(app, storageStrategy);
            }

            // 5. 清理其他临时目录
            cleanupTempDirectories(app);

            // 6. 删除数据库记录
            boolean result = this.removeById(appId);
            if (result) {
                log.info("应用删除成功，包括关联文件和任务清理完成, appId: {}", appId);
            } else {
                log.error("应用数据库删除失败, appId: {}", appId);
            }
            return result;
        } catch (Exception e) {
            log.error("删除应用时发生异常, appId: {}, error: {}", appId, e.getMessage(), e);
            return false;
        }
    }

    /**
     * 重写删除方法，关联删除对话历史
     * @param id
     * @return
     */
    @Override
    public boolean removeById(Serializable id) {
        if (id == null) {
            return false;
        }
        Long appId = Long.parseLong(id.toString());
        if (appId <= 0) {
            return false;
        }
        try {
            // 关联删除对话历史
            boolean result = chatHistoryService.deleteByAppId(appId);
            if (result) {
                log.info("关联删除对话历史成功, appId: {}", appId);
            }
        } catch (Exception e) {
            log.error("删除应用时，关联删除对话历史失败, appId: {}, error: {}", appId, e.getMessage());
        }
        // 调用父类方法删除应用
        return super.removeById(appId);
    }

    /**
     * COS策略下的文件清理：先删除本地临时文件，再删除COS存储
     *
     * @param app 应用信息
     * @param storageStrategy 存储策略
     */
    private void cleanupCosStrategyFiles(App app, FileStorageStrategy storageStrategy) {
        try {
            String userId = String.valueOf(app.getUserId());
            String appId = String.valueOf(app.getId());
            List<Path> localPathsToDelete = new ArrayList<>();

            // 1. 收集需要删除的本地临时文件路径
            // code_output 临时文件
            String codeGenType = app.getCodeGenType();
            if (StrUtil.isNotBlank(codeGenType)) {
                String outputDirName = codeGenType + "_" + appId;
                String localTempOutputPath = storageStrategy.buildCodeOutputPath(userId, outputDirName);
                Path localPath = Paths.get(localTempOutputPath);
                if (Files.exists(localPath)) {
                    localPathsToDelete.add(localPath);
                }
            }

            // code_deploy 临时文件
            String deployKey = app.getDeployKey();
            if (StrUtil.isNotBlank(deployKey)) {
                String localTempDeployPath = storageStrategy.buildCodeDeployPath(userId, deployKey);
                Path localPath = Paths.get(localTempDeployPath);
                if (Files.exists(localPath)) {
                    localPathsToDelete.add(localPath);
                }
            }

            // 批量删除本地文件（优化性能）
            if (!localPathsToDelete.isEmpty()) {
                log.info("COS策略：开始批量删除 {} 个本地临时目录", localPathsToDelete.size());
                int deletedCount = tempDirectoryService.batchDeleteDirectories(localPathsToDelete);
                log.info("COS策略：批量删除本地临时目录完成，成功删除 {}/{} 个目录", deletedCount, localPathsToDelete.size());
            }

            // 2. 删除COS存储中的文件
            // 需要调用COS特定的删除方法
            if (storageStrategy instanceof CosFileStorageStrategy) {
                // 删除COS中的code_output目录
                if (StrUtil.isNotBlank(codeGenType)) {
                    String outputDirName = codeGenType + "_" + appId;
                    // 构建COS对象键（不包含域名）
                    String cosOutputKey = "userApp/" + userId + "/code_output/" + outputDirName;
                    try {
                        // 使用目录删除方法删除整个目录
                        boolean deleted = cosOperationService.deleteDirectory(cosOutputKey);
                        if (deleted) {
                            log.info("COS策略：删除COS存储code_output目录成功: {}", cosOutputKey);
                        } else {
                            log.debug("COS策略：COS存储code_output目录不存在或为空: {}", cosOutputKey);
                        }
                    } catch (Exception e) {
                        log.debug("COS策略：COS存储code_output目录删除失败: {}, error: {}", cosOutputKey, e.getMessage());
                    }
                }

                // 删除COS中的code_deploy目录
                if (StrUtil.isNotBlank(deployKey)) {
                    // 构建COS对象键（不包含域名）
                    String cosDeployKey = "userApp/" + userId + "/code_deploy/" + deployKey;
                    try {
                        // 使用目录删除方法删除整个目录
                        boolean deleted = cosOperationService.deleteDirectory(cosDeployKey);
                        if (deleted) {
                            log.info("COS策略：删除COS存储code_deploy目录成功: {}", cosDeployKey);
                        } else {
                            log.debug("COS策略：COS存储code_deploy目录不存在或为空: {}", cosDeployKey);
                        }
                    } catch (Exception e) {
                        log.debug("COS策略：COS存储code_deploy目录删除失败: {}, error: {}", cosDeployKey, e.getMessage());
                    }
                }
            }

        } catch (Exception e) {
            log.error("COS策略文件清理失败, appId: {}, error: {}", app.getId(), e.getMessage(), e);
        }
    }

    /**
     * Local策略下的文件清理：只删除本地文件
     *
     * @param app 应用信息
     * @param storageStrategy 存储策略
     */
    private void cleanupLocalStrategyFiles(App app, FileStorageStrategy storageStrategy) {
        try {
            String userId = String.valueOf(app.getUserId());
            String appId = String.valueOf(app.getId());

            // 删除本地code_output文件
            String codeGenType = app.getCodeGenType();
            if (StrUtil.isNotBlank(codeGenType)) {
                String outputDirName = codeGenType + "_" + appId;
                String localOutputPath = storageStrategy.buildCodeOutputPath(userId, outputDirName);

                try {
                    storageStrategy.deleteFile(localOutputPath);
                    log.info("Local策略：删除本地code_output目录成功: {}", localOutputPath);
                } catch (Exception e) {
                    log.debug("Local策略：本地code_output目录不存在或删除失败: {}, error: {}", localOutputPath, e.getMessage());
                }
            }

            // 删除本地code_deploy文件
            String deployKey = app.getDeployKey();
            if (StrUtil.isNotBlank(deployKey)) {
                String localDeployPath = storageStrategy.buildCodeDeployPath(userId, deployKey);

                try {
                    storageStrategy.deleteFile(localDeployPath);
                    log.info("Local策略：删除本地code_deploy目录成功: {}", localDeployPath);
                } catch (Exception e) {
                    log.debug("Local策略：本地code_deploy目录不存在或删除失败: {}, error: {}", localDeployPath, e.getMessage());
                }
            }

        } catch (Exception e) {
            log.error("Local策略文件清理失败, appId: {}, error: {}", app.getId(), e.getMessage(), e);
        }
    }

    /**
     * 清理临时目录
     *
     * @param app 应用信息
     */
    private void cleanupTempDirectories(App app) {
        try {
            String appId = String.valueOf(app.getId());
            // 清理格式为 {projectRoot}/tmp/cos/cos_build_{appId} 的临时目录
            Path projectRoot = Paths.get(System.getProperty("user.dir"));
            Path appTempDir = projectRoot.resolve("tmp/cos/cos_build_" + appId);

            if (Files.exists(appTempDir) && Files.isDirectory(appTempDir)) {
                // 使用批量删除优化性能
                List<Path> pathsToDelete = List.of(appTempDir);
                int deletedCount = tempDirectoryService.batchDeleteDirectories(pathsToDelete);
                if (deletedCount > 0) {
                    log.info("批量清理应用临时目录成功: {}", appTempDir);
                } else {
                    log.warn("批量清理应用临时目录失败: {}", appTempDir);
                }
            } else {
                log.debug("应用临时目录不存在: {}", appTempDir);
            }
        } catch (Exception e) {
            log.error("清理临时目录失败, appId: {}, error: {}", app.getId(), e.getMessage());
        }
    }

    /**
     * 中断AI生成任务
     * @param appId 应用ID
     * @param taskId 任务ID
     * @param loginUser 登录用户
     * @return 是否成功中断
     */
    @Override
    public boolean interruptGeneration(Long appId, String taskId, User loginUser) {
        // 1. 参数校验
        ThrowUtils.throwIf(appId == null || appId <= 0, ErrorCode.PARAMS_ERROR, "应用ID错误");
        ThrowUtils.throwIf(StrUtil.isBlank(taskId), ErrorCode.PARAMS_ERROR, "任务ID不能为空");
        ThrowUtils.throwIf(loginUser == null, ErrorCode.NOT_LOGIN_ERROR, "用户未登录");

        // 2. 查询应用信息
        App app = this.getById(appId);
        ThrowUtils.throwIf(app == null, ErrorCode.NOT_FOUND_ERROR, "应用不存在");

        // 3. 权限校验，仅本人可以中断自己的应用生成任务
        if (!app.getUserId().equals(loginUser.getId())) {
            throw new BusinessException(ErrorCode.NO_AUTH_ERROR, "无权限中断该应用的生成任务");
        }

        // 4. 检查任务是否存在于任务管理器中（优先检查）
        String taskStatus = taskManagerService.getTaskStatus(taskId);
        if ("NOT_FOUND".equals(taskStatus)) {
            // 任务不存在，可能已经完成或从未启动
            log.warn("尝试中断不存在的任务，taskId: {}, appId: {}", taskId, appId);
            return false;
        }

        // 5. 验证数据库中的任务ID（如果任务正在运行，应该匹配；如果已经被清空，但任务管理器中还存在，也允许中断）
        String currentTaskId = app.getCurrentTaskId();
        if (StrUtil.isNotBlank(currentTaskId) && !taskId.equals(currentTaskId)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "任务ID不匹配");
        }

        // 6. 调用任务管理服务中断任务
        boolean result = taskManagerService.interruptTask(taskId, appId);

        if (result) {
            log.info("成功中断生成任务，appId: {}, taskId: {}, userId: {}", appId, taskId, loginUser.getId());
            // 确保数据库状态正确更新为中断状态
            updateAppTaskStatus(appId, null, AppStatusConstant.GENERATE_STATUS_INTERRUPTED);
        } else {
            log.warn("中断生成任务失败，appId: {}, taskId: {}, userId: {}", appId, taskId, loginUser.getId());
        }

        return result;
    }

    /**
     * 下线应用（取消部署）
     * @param appId 应用ID
     * @param loginUser 登录用户
     * @return 是否成功下线
     */
    @Override
    public boolean undeployApp(Long appId, User loginUser) {
        // 1. 参数校验
        ThrowUtils.throwIf(appId == null || appId <= 0, ErrorCode.PARAMS_ERROR, "应用ID错误");
        ThrowUtils.throwIf(loginUser == null, ErrorCode.NOT_LOGIN_ERROR, "用户未登录");

        // 2. 查询应用信息
        App app = this.getById(appId);
        ThrowUtils.throwIf(app == null, ErrorCode.NOT_FOUND_ERROR, "应用不存在");

        // 3. 权限校验，仅本人可以下线自己的应用
        if (!app.getUserId().equals(loginUser.getId())) {
            throw new BusinessException(ErrorCode.NO_AUTH_ERROR, "无权限下线该应用");
        }

        // 4. 检查应用是否已部署
        if (StrUtil.isBlank(app.getDeployKey())) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "应用尚未部署，无需下线");
        }

        try {
            // 5. 释放监控资源（定时清理任务和会话监控）
            try {
                tempFileLifecycleObserver.manualCleanupByAppId(String.valueOf(appId), String.valueOf(app.getUserId()));
                log.info("下线应用时成功释放监控资源, appId: {}, userId: {}", appId, app.getUserId());
            } catch (Exception e) {
                log.warn("下线应用时释放监控资源失败, appId: {}, userId: {}, error: {}", appId, app.getUserId(), e.getMessage());
            }

            // 6. 根据存储策略执行不同的下线清理逻辑
            FileStorageStrategy storageStrategy = fileStorageStrategyFactory.getCurrentStrategy();
            if (StorageType.COS.getCode().equals(storageStrategy.getStorageType().getCode())) {
                // COS策略：删除本地临时文件 + COS存储文件
                undeployWithCosStrategy(app, storageStrategy);
            } else {
                // Local策略：只删除本地文件
                undeployWithLocalStrategy(app, storageStrategy);
            }

            // 7. 更新数据库，清空部署相关字段
            // 使用UpdateChain强制更新null值
            boolean result = UpdateChain.of(App.class)
                    .set(App::getDeployKey, null)
                    .set(App::getDeployedTime, null)
                    .set(App::getDeployStatus, AppStatusConstant.DEPLOY_STATUS_NOT_DEPLOYED)
                    .set(App::getEditTime, LocalDateTime.now())
                    .where(App::getId).eq(appId)
                    .update();

            if (result) {
                log.info("应用下线成功，appId: {}, userId: {}, 已清空deployKey和deployedTime", appId, loginUser.getId());
            } else {
                log.error("应用下线失败，数据库更新失败，appId: {}", appId);
            }

            return result;
        } catch (Exception e) {
            log.error("应用下线失败，appId: {}, error: {}", appId, e.getMessage(), e);
            return false;
        }
    }

    /**
     * 获取应用生成状态
     * @param appId 应用ID
     * @param loginUser 登录用户
     * @return 生成状态信息
     */
    @Override
    public String getGenerationStatus(Long appId, User loginUser) {
        // 1. 参数校验
        ThrowUtils.throwIf(appId == null || appId <= 0, ErrorCode.PARAMS_ERROR, "应用ID错误");
        ThrowUtils.throwIf(loginUser == null, ErrorCode.NOT_LOGIN_ERROR, "用户未登录");

        // 2. 查询应用信息
        App app = this.getById(appId);
        ThrowUtils.throwIf(app == null, ErrorCode.NOT_FOUND_ERROR, "应用不存在");

        // 3. 权限校验，仅本人可以查看自己的应用状态
        if (!app.getUserId().equals(loginUser.getId())) {
            throw new BusinessException(ErrorCode.NO_AUTH_ERROR, "无权限查看该应用状态");
        }

        // 4. 获取生成状态
        Integer generateStatus = app.getGenerateStatus();
        String currentTaskId = app.getCurrentTaskId();

        // 5. 如果有正在进行的任务，检查任务实际状态
        if (generateStatus != null && generateStatus.equals(AppStatusConstant.GENERATE_STATUS_GENERATING)
                && StrUtil.isNotBlank(currentTaskId)) {
            String taskStatus = taskManagerService.getTaskStatus(currentTaskId);
            if ("NOT_FOUND".equals(taskStatus)) {
                // 任务管理器中找不到任务，可能已经完成或异常，更新状态
                App updateApp = new App();
                updateApp.setId(appId);
                updateApp.setGenerateStatus(AppStatusConstant.GENERATE_STATUS_FAILED);
                updateApp.setCurrentTaskId(null);
                updateApp.setEditTime(LocalDateTime.now());
                this.updateById(updateApp);
                return "FAILED";
            }
            return taskStatus;
        }

        // 6. 根据状态码返回状态描述
        if (generateStatus == null) {
            return "NOT_GENERATED";
        }

        return switch (generateStatus) {
            case 0 -> "NOT_GENERATED";
            case 1 -> "GENERATING";
            case 2 -> "COMPLETED";
            case 3 -> "FAILED";
            case 4 -> "INTERRUPTED";
            default -> "UNKNOWN";
        };
    }

    /**
     * 更新应用的任务状态
     * @param appId 应用ID
     * @param taskId 任务ID（可以为null，表示清除任务ID）
     * @param status 生成状态
     */
    private void updateAppTaskStatus(Long appId, String taskId, Integer status) {
        try {
            App updateApp = new App();
            updateApp.setId(appId);
            updateApp.setCurrentTaskId(taskId); // taskId可以为null，用于清除任务ID
            updateApp.setGenerateStatus(status);
            updateApp.setEditTime(LocalDateTime.now());

            boolean result = this.updateById(updateApp);
            if (result) {
                log.info("更新应用任务状态成功，appId: {}, taskId: {}, status: {}", appId, taskId, status);
            } else {
                log.error("更新应用任务状态失败，appId: {}, taskId: {}, status: {}", appId, taskId, status);
            }
        } catch (Exception e) {
            log.error("更新应用任务状态异常，appId: {}, taskId: {}, status: {}, error: {}",
                    appId, taskId, status, e.getMessage());
        }
    }

    /**
     * 上传部署结果到COS
     */
    private void uploadDeployResultToCos(Long userId, String deployKey, String localDeployPath) {
        try {
            String cosPath = "userApp/" + userId + "/code_deploy/" + deployKey + "/";
            boolean uploadSuccess = cosOperationService.uploadBuildResults(localDeployPath, cosPath);

            if (uploadSuccess) {
                log.info("上传部署结果到COS成功，userId: {}, deployKey: {}, cosPath: {}", userId, deployKey, cosPath);
            } else {
                log.warn("上传部署结果到COS失败，userId: {}, deployKey: {}, cosPath: {}", userId, deployKey, cosPath);
                throw new BusinessException(ErrorCode.SYSTEM_ERROR, "上传部署结果到COS失败");
            }
        } catch (Exception e) {
            log.error("上传部署结果到COS失败，userId: {}, deployKey: {}, error: {}", userId, deployKey, e.getMessage());
            throw new BusinessException(ErrorCode.SYSTEM_ERROR, "上传部署结果到COS失败");
        }
    }

    /**
     * 从COS下载临时文件到本地（高性能并发版本）
     * 清理逻辑已整合到CosOperationService.downloadDirectoryFromCosConcurrent方法中
     */
    private void downloadFromCosIfNeeded(Long userId, Long appId, String codeGenType, String localBasePath) {
        try {
            // 构建COS路径
            String projectName = codeGenType + "_" + appId;
            String cosPath = "userApp/" + userId + "/code_output/" + projectName;

            log.info("开始从COS并发下载项目文件: userId={}, appId={}, cosPath={}, localPath={}",
                    userId, appId, cosPath, localBasePath);

            // 确保本地目录存在
            Files.createDirectories(Paths.get(localBasePath));

            // 创建性能监控回调
            performanceMonitoringCallback.initialize(null, null);

            // 从COS并发下载整个项目目录
            cosOperationService.downloadDirectoryFromCosConcurrent(cosPath, localBasePath, performanceMonitoringCallback);

            log.info("从COS并发下载项目文件完成: userId={}, appId={}, localPath={}", userId, appId, localBasePath);

        } catch (Exception e) {
            log.error("从COS并发下载项目文件失败: userId={}, appId={}, error={}", userId, appId, e.getMessage(), e);
            throw new RuntimeException("从COS下载项目文件失败: " + e.getMessage(), e);
        }
    }

    /**
     * 本地文件系统目录复制方法
     * 专门用于部署时的文件复制，不依赖存储策略
     * @param sourceDir 源目录路径
     * @param targetDir 目标目录路径
     * @throws IOException IO异常
     */
    private void copyDirectoryLocally(String sourceDir, String targetDir) throws IOException {
        Path sourcePath = Paths.get(sourceDir);
        Path targetPath = Paths.get(targetDir);

        if (!Files.exists(sourcePath)) {
            throw new IOException("源目录不存在: " + sourceDir);
        }

        // 创建目标目录
        Files.createDirectories(targetPath);

        int copiedCount = 0;

        // 递归复制文件和目录
        try (Stream<Path> paths = Files.walk(sourcePath)) {
            for (Path source : paths.toList()) {
                try {
                    Path target = targetPath.resolve(sourcePath.relativize(source));
                    if (Files.isDirectory(source)) {
                        Files.createDirectories(target);
                    } else {
                        Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
                        copiedCount++;
                    }
                } catch (IOException e) {
                    log.error("复制文件失败: {} -> {}", source, targetPath.resolve(sourcePath.relativize(source)), e);
                    throw e;
                }
            }
        }

        log.info("目录复制完成: {} -> {}, 共复制 {} 个文件", sourceDir, targetDir, copiedCount);
    }

    /**
     * COS策略下的应用下线处理
     * 只删除部署相关的文件目录（code_deploy），保留代码生成结果（code_output）
     */
    private void undeployWithCosStrategy(App app, FileStorageStrategy storageStrategy) {
        String userId = String.valueOf(app.getUserId());
        String deployKey = app.getDeployKey();

        // 只处理code_deploy相关的删除操作
        if (StrUtil.isNotBlank(deployKey)) {
            // 1. 删除本地临时code_deploy目录（使用批量删除优化）
            try {
                String deployPath = storageStrategy.buildCodeDeployPath(userId, deployKey);
                Path localPath = Paths.get(deployPath);

                if (Files.exists(localPath)) {
                    List<Path> pathsToDelete = List.of(localPath);
                    int deletedCount = tempDirectoryService.batchDeleteDirectories(pathsToDelete);
                    if (deletedCount > 0) {
                        log.info("COS策略下线：批量删除本地临时code_deploy目录成功: {}", deployPath);
                    } else {
                        log.warn("COS策略下线：批量删除本地临时code_deploy目录失败: {}", deployPath);
                    }
                } else {
                    log.debug("COS策略下线：本地临时code_deploy目录不存在: {}", deployPath);
                }
            } catch (Exception e) {
                log.warn("COS策略下线：删除本地临时code_deploy目录失败: {}", e.getMessage());
            }

            // 2. 删除COS存储中的code_deploy目录
            try {
                String cosDeployKey = "userApp/" + userId + "/code_deploy/" + deployKey;
                boolean deleted = cosOperationService.deleteDirectory(cosDeployKey);
                if (deleted) {
                    log.info("COS策略下线：删除COS存储code_deploy目录成功: {}", cosDeployKey);
                } else {
                    log.warn("COS策略下线：COS存储code_deploy目录不存在或为空: {}", cosDeployKey);
                }
            } catch (Exception e) {
                log.warn("COS策略下线：删除COS存储code_deploy目录失败: {}", e.getMessage());
            }
        }
    }

    /**
     * Local策略下的应用下线处理
     * 只删除部署相关的文件目录（code_deploy），保留代码生成结果（code_output）
     */
    private void undeployWithLocalStrategy(App app, FileStorageStrategy storageStrategy) {
        String userId = String.valueOf(app.getUserId());
        String deployKey = app.getDeployKey();

        // 只删除本地code_deploy目录
        if (StrUtil.isNotBlank(deployKey)) {
            try {
                String deployPath = storageStrategy.buildCodeDeployPath(userId, deployKey);
                storageStrategy.deleteFile(deployPath);
                log.info("Local策略下线：删除本地code_deploy目录成功: {}", deployPath);
            } catch (Exception e) {
                log.warn("Local策略下线：删除本地code_deploy目录失败: {}", e.getMessage());
            }
        }
    }

    /**
     * 准备应用代码用于下载
     * 在COS策略模式下，优先检查本地临时目录是否存在，如果不存在则从COS下载
     *
     * @param app 应用信息
     * @return 本地代码目录路径
     */
    @Override
    public String prepareAppCodeForDownload(App app) {
        // 1. 获取存储策略
        FileStorageStrategy storageStrategy = fileStorageStrategyFactory.getCurrentStrategy();

        // 2. 构建应用代码目录路径
        String codeGenType = app.getCodeGenType();
        String sourceDirName = codeGenType + "_" + app.getId();
        String sourceDirPath = storageStrategy.buildCodeOutputPath(String.valueOf(app.getUserId()), sourceDirName);

        // 3. 检查本地目录是否存在且包含文件
        File sourceDir = new File(sourceDirPath);
        boolean needDownload = !sourceDir.exists() ||
                !sourceDir.isDirectory() ||
                (sourceDir.listFiles() == null || Objects.requireNonNull(sourceDir.listFiles()).length == 0);

        // 4. 如果是COS策略且需要下载，则从COS拉取文件（使用高性能并发下载）
        if (needDownload && StorageType.COS.getCode().equals(storageStrategy.getStorageType().getCode())) {
            log.info("本地代码目录不存在或为空，从COS并发下载: appId={}, path={}", app.getId(), sourceDirPath);
            downloadFromCosIfNeeded(app.getUserId(), app.getId(), codeGenType, sourceDirPath);

            // 重新检查目录是否存在
            sourceDir = new File(sourceDirPath);
            ThrowUtils.throwIf(!sourceDir.exists() || !sourceDir.isDirectory() ||
                            (sourceDir.listFiles() == null || Objects.requireNonNull(sourceDir.listFiles()).length == 0),
                    ErrorCode.NOT_FOUND_ERROR, "应用代码不存在，请先生成代码");
        }

        // 5. 本地策略或COS下载完成后，验证目录存在
        ThrowUtils.throwIf(!sourceDir.exists() || !sourceDir.isDirectory(),
                ErrorCode.NOT_FOUND_ERROR, "应用代码不存在，请先生成代码");

        log.info("应用代码准备完成: appId={}, path={}", app.getId(), sourceDirPath);
        return sourceDirPath;
    }
}
